﻿#region License
// <copyright file="ExpressionExtensions.cs" company="Kopigi - Marc Plessis">
// Copyright © Kopigi 2011
// </copyright>
// ****************************************************************************
// <author>Marc Plessis</author>
// <email>marc.plessis@kopigi.fr</email>
// <date>03/04/2011</date>
// <project>Biwo</project>
// <web>http://www.kopigi.fr/biwo</web>
// <license>
// See license.txt in this solution or http://www.microsoft.com/opensource/licenses.mspx
// </license>
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

/**
 *  Inspire by http://www.albahari.com/nutshell/linqkit.html and  http://tomasp.net/blog/linq-expand.aspx
 * 
 * */
namespace SQLServer.Repository.DAL.RepositoryDao.Extensions
{
    public static class ExpressionExtensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
                  (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
        }

        /// <summary>
        /// Return parameter as an <code>Expression<Func<T, TValue>></code>
        /// </summary>
        /// <typeparam name="T">Type of filter</typeparam>
        /// <typeparam name="TValue">Type of filter's values</typeparam>
        /// <param name="expr"></param>
        /// <returns></returns>
        public static Expression<Func<T, TValue>> FilterValues<T, TValue>(Expression<Func<T, TValue>> expr)
        {
            return expr;
        }

        /// <summary>
        /// Used to include a list of values
        /// </summary>
        /// <typeparam name="T">Filter Type</typeparam>
        /// <typeparam name="TValue">Values Type</typeparam>
        /// <param name="valueSelector">Filter</param>
        /// <param name="values">Included values</param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> In<T, TValue>(this Expression<Func<T, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            ParameterExpression p = valueSelector.Parameters.Single();
            if (!values.Any())
            {
                return e => false;
            }
            var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<Expression>(Expression.Or);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        /// <summary>
        /// Return parameter as an <code>Expression<Func<T, bool>></code>
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="expr"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> New<T>(Expression<Func<T, bool>> expr)
        {
            return expr;
        }

        /// <summary>
        /// Used to exclude a list of values
        /// </summary>
        /// <typeparam name="T">Filter Type</typeparam>
        /// <typeparam name="TValue">Values Type</typeparam>
        /// <param name="valueSelector">Filter</param>
        /// <param name="values">Excluded values</param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> NotIn<T, TValue>(this Expression<Func<T, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            ParameterExpression p = valueSelector.Parameters.Single();
            if (!values.Any())
            {
                return e => false;
            }
            var equals = values.Select(value => (Expression)Expression.NotEqual(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<Expression>(Expression.And);
            return Expression.Lambda<Func<T, bool>>(body, p);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
                  (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
        }
    }
}
